import { isNotNil } from './utils/check';
import { isValid } from 'date-fns';

export class TimeHolder {
    hourStep = 1;

    minuteStep = 1;

    secondStep = 1;

    use12Hours = false;

    private _hours: number | undefined = undefined;

    /**
     * @description
     * Same as realHours
     * @see realHours
     */
    get hours(): number | undefined {
        return this._hours;
    }

    /**
     * @description
     * Set viewHours to realHours
     */
    set hours(value: number | undefined) {
        if (value !== this._hours) {
            if (this.use12Hours) {
                if (this.selected12Hours === 'PM' && value !== 12) {
                    this._hours! = (value as number) + 12;
                } else if (this.selected12Hours === 'AM' && value === 12) {
                    this._hours = 0;
                } else {
                    this._hours = value;
                }
            } else {
                this._hours = value;
            }
            this.update();
        }
    }

    /**
     * @description
     * Value hours
     * Get realHours and its range is [0, 1, 2, ..., 22, 23]
     */
    get realHours(): number | undefined {
        return this._hours;
    }

    /**
     * @description
     * UI view hours
     * Get viewHours which is selected in `time-picker-panel` and its range is [12, 1, 2, ..., 11]
     */
    get viewHours(): number | undefined {
        return this.use12Hours && isNotNil(this._hours) ? this.calculateViewHour(this._hours!) : this._hours;
    }

    private _minutes: number | undefined = undefined;

    get minutes(): number | undefined {
        return this._minutes;
    }

    set minutes(value: number | undefined) {
        if (value !== this._minutes) {
            this._minutes = value;
            this.update();
        }
    }

    private _seconds: number | undefined = undefined;

    get seconds(): number | undefined {
        return this._seconds;
    }

    set seconds(value: number | undefined) {
        if (value !== this._seconds) {
            this._seconds = value;
            this.update();
        }
    }

    /**
     * @description
     * Same as defaultRealHours
     */
    get defaultHours(): number {
        return this.defaultOpenValue.getHours();
    }

    /**
     * @description
     * Get deafultViewHours when defaultOpenValue is setted
     * @see viewHours
     */
    get defaultViewHours(): number {
        const hours = this.defaultOpenValue.getHours();
        return this.use12Hours && isNotNil(hours) ? this.calculateViewHour(hours) : hours;
    }

    /**
     * @description
     * Get defaultRealHours when defaultOpenValue is setted
     * @see realHours
     */
    get defaultRealHours(): number {
        return this.defaultOpenValue.getHours();
    }

    get defaultMinutes(): number {
        return this.defaultOpenValue.getMinutes();
    }

    get defaultSeconds(): number {
        return this.defaultOpenValue.getSeconds();
    }

    get default12Hours(): string {
        return this.defaultOpenValue.getHours() >= 12 ? 'PM' : 'AM';
    }

    private _text = '';

    get text(): string {
        return this._text;
    }

    set text(value: string) {
        if (value !== this._text) {
            this._text = value;
        }
    }

    private _value: Date | undefined;

    get value(): Date | undefined {
        return this._value;
    }

    set value(value: Date | undefined) {
        if (value !== this._value) {
            this._value = value;
            if (isNotNil(this._value) && isValid(this._value)) {
                // tslint:disable-next-line:no-non-null-assertion
                this._hours = this._value!.getHours();
                this._minutes = this._value!.getMinutes();
                this._seconds = this._value!.getSeconds();
                if (this.use12Hours && isNotNil(this._hours)) {
                    this._selected12Hours = this._hours >= 12 ? 'PM' : 'AM';
                }
            } else {
                this.clearTimeValue();
            }
        }
    }

    private _selected12Hours: string | undefined = undefined;

    get selected12Hours(): string | undefined {
        return this._selected12Hours;
    }

    set selected12Hours(value: string | undefined) {
        if (value!.toUpperCase() !== this._selected12Hours) {
            this._selected12Hours = value!.toUpperCase();
            this.update();
        }
    }

    private _defaultOpenValue: Date = new Date();

    get defaultOpenValue(): Date {
        this._defaultOpenValue.setHours(this.setValueByStep(this._defaultOpenValue.getHours(), this.hourStep));
        this._defaultOpenValue.setMinutes(this.setValueByStep(this._defaultOpenValue.getMinutes(), this.minuteStep));
        this._defaultOpenValue.setSeconds(this.setValueByStep(this._defaultOpenValue.getSeconds(), this.secondStep));
        return this._defaultOpenValue;
    }

    set defaultOpenValue(value: Date) {
        if (this._defaultOpenValue !== value) {
            this._defaultOpenValue = value;
            this.update();
        }
    }

    get isEmpty(): boolean {
        return !(isNotNil(this._hours) || isNotNil(this._minutes) || isNotNil(this._seconds));
    }

    constructor() {}

    setHours(value: number, disabled: boolean): this {
        if (disabled) {
            return this;
        }
        this.setDefaultValueIfNil();
        this.hours = value;
        return this;
    }

    setUse12Hours(value: boolean): this {
        this.use12Hours = value;
        return this;
    }

    setMinutes(value: number, disabled: boolean): this {
        if (disabled) {
            return this;
        }
        this.setDefaultValueIfNil();
        this.minutes = value;
        return this;
    }

    setSeconds(value: number, disabled: boolean): this {
        if (disabled) {
            return this;
        }
        this.setDefaultValueIfNil();
        this.seconds = value;
        return this;
    }

    setValue(value: Date | undefined, use12Hours?: boolean): this {
        if (isNotNil(use12Hours)) {
            this.use12Hours = use12Hours as boolean;
        }
        this.value = value;
        return this;
    }

    setValueByStep(value: number, step: number) {
        let times = Math.floor(value / step);
        const remainder = value % step;
        const halfStep = step / 2;
        times = remainder > halfStep ? times + 1 : times;
        return step * times;
    }

    setDefaultOpenValue(value: Date): this {
        this.defaultOpenValue = value;
        return this;
    }

    setDefaultValueIfNil(): void {
        if (!isNotNil(this._value)) {
            this._value = new Date(this.defaultOpenValue);
        }
    }

    private update(): void {
        if (this.isEmpty) {
            this._value = undefined;
        } else {
            if (!isNotNil(this._hours)) {
                this._hours = this.defaultHours;
            } else {
                this._value!.setHours(this.hours!);
            }

            if (!isNotNil(this._minutes)) {
                this._minutes = this.defaultMinutes;
            } else {
                this._value!.setMinutes(this.minutes!);
            }

            if (!isNotNil(this._seconds)) {
                this._seconds = this.defaultSeconds;
            } else {
                this._value!.setSeconds(this.seconds!);
            }

            if (this.use12Hours) {
                if (!isNotNil(this._selected12Hours)) {
                    this._selected12Hours = this.default12Hours;
                }
                if (this.selected12Hours === 'PM' && this._hours! < 12) {
                    this._hours! += 12;
                    this._value!.setHours(this._hours!);
                }
                if (this.selected12Hours === 'AM' && this._hours! >= 12) {
                    this._hours! -= 12;
                    this._value!.setHours(this._hours!);
                }
            }

            this._value = new Date(this._value!);
        }
    }

    private clearTimeValue() {
        this._hours = undefined;
        this._minutes = undefined;
        this._seconds = undefined;
        this._selected12Hours = undefined;
        this._value = undefined;
    }

    clear(): void {
        this.clearTimeValue();
        this.update();
    }

    private calculateViewHour(value: number): number {
        const selected12Hours = this._selected12Hours || this.default12Hours;
        if (selected12Hours === 'PM' && value > 12) {
            return value - 12;
        }
        if (selected12Hours === 'AM' && value === 0) {
            return 12;
        }
        return value;
    }
}
